package com.slfx.open.api.sdk.utils;

import com.slfx.open.api.sdk.common.exception.ErrorCodeEnum;
import com.slfx.open.api.sdk.common.exception.CommonException;
import org.apache.commons.lang3.StringUtils;

import java.io.*;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;

/**
 * 签名验签工具类
 *
 * @author slfx
 * @create 2021/2/22 21:59
 */
public class RsaUtils {
    /**
     * 秘钥对算法名称
     */
    private static final String ALGORITHM = "RSA";

    /**
     * 密钥长度
     */
    private static final int KEY_SIZE = 1024;

    /**
     * 签名算法
     */
    private static final String SIGNATURE_ALGORITHM = "Sha1WithRSA";

    /**
     * 随机生成 RSA 密钥对（包含公钥和私钥）
     */
    public static String[] generateKeyPair() throws Exception {
        // 获取指定算法的密钥对生成器
        KeyPairGenerator gen = KeyPairGenerator.getInstance(ALGORITHM);

        // 初始化密钥对生成器（指定密钥长度, 使用默认的安全随机数源）
        gen.initialize(KEY_SIZE);

        // 随机生成一对密钥（包含公钥和私钥）
        /** 生成一个密钥对，保存在keyPair中  */
        KeyPair keyPair = gen.generateKeyPair();
        // 得到私钥
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        // 得到公钥
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        String publicKeyString = new String(Base64.getEncoder().encode(publicKey.getEncoded()));
        /** 得到私钥字符串 */
        String privateKeyString = new String(Base64.getEncoder().encode((privateKey.getEncoded())));
        return new String[]{publicKeyString, privateKeyString};

    }

    /**
     * 私钥签名（数据）: 用私钥对指定字节数组数据进行签名, 返回签名信息
     */
    public static byte[] sign(byte[] data, PrivateKey priKey) throws Exception {
        // 根据指定算法获取签名工具
        Signature sign = Signature.getInstance(SIGNATURE_ALGORITHM);

        // 用私钥初始化签名工具
        sign.initSign(priKey);

        // 添加要签名的数据
        sign.update(data);

        // 计算签名结果（签名信息）
        byte[] signInfo = sign.sign();

        return signInfo;
    }

    /**
     * 公钥验签（数据）: 用公钥校验指定数据的签名是否来自对应的私钥
     */
    public static boolean verify(byte[] data, byte[] signInfo, PublicKey pubKey) throws Exception {
        // 根据指定算法获取签名工具
        Signature sign = Signature.getInstance(SIGNATURE_ALGORITHM);

        // 用公钥初始化签名工具
        sign.initVerify(pubKey);

        // 添加要校验的数据
        sign.update(data);

        // 校验数据的签名信息是否正确,
        // 如果返回 true, 说明该数据的签名信息来自该公钥对应的私钥,
        // 同一个私钥的签名, 数据和签名信息一一对应, 只要其中有一点修改, 则用公钥无法校验通过,
        // 因此可以用私钥签名, 然后用公钥来校验数据的完整性与签名者（所有者）
        return sign.verify(signInfo);
    }

    /**
     * 私钥签名（文件）: 用私钥对文件进行签名, 返回签名信息
     */
    public static byte[] signFile(File file, PrivateKey priKey) throws Exception {
        // 根据指定算法获取签名工具
        Signature sign = Signature.getInstance(SIGNATURE_ALGORITHM);

        // 用私钥初始化签名工具
        sign.initSign(priKey);

        InputStream in = null;

        try {
            in = new FileInputStream(file);

            byte[] buf = new byte[1024];
            int len = -1;

            while ((len = in.read(buf)) != -1) {
                // 添加要签名的数据
                sign.update(buf, 0, len);
            }

        } finally {
            close(in);
        }

        // 计算并返回签名结果（签名信息）
        return sign.sign();
    }

    /**
     * 公钥验签（文件）: 用公钥校验指定文件的签名是否来自对应的私钥
     */
    public static boolean verifyFile(File file, byte[] signInfo, PublicKey pubKey) throws Exception {
        // 根据指定算法获取签名工具
        Signature sign = Signature.getInstance(SIGNATURE_ALGORITHM);

        // 用公钥初始化签名工具
        sign.initVerify(pubKey);

        InputStream in = null;

        try {
            in = new FileInputStream(file);

            byte[] buf = new byte[1024];
            int len = -1;

            while ((len = in.read(buf)) != -1) {
                // 添加要校验的数据
                sign.update(buf, 0, len);
            }

        } finally {
            close(in);
        }

        // 校验签名
        return sign.verify(signInfo);
    }

    private static void close(Closeable c) {
        if (c != null) {
            try {
                c.close();
            } catch (IOException e) {
                // nothing
            }
        }
    }

    public static PublicKey getPublicKeyByStr(String publicKeyStr) throws NoSuchAlgorithmException, InvalidKeySpecException {
        byte[] publicBytes = Base64.getDecoder().decode(publicKeyStr);
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        PublicKey pubKey = keyFactory.generatePublic(keySpec);
        return pubKey;
    }


    public static PrivateKey getPrivateKeyByStr(String privateKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
        byte[] priBytes = Base64.getDecoder().decode(privateKey);
        PKCS8EncodedKeySpec priKeySpec = new PKCS8EncodedKeySpec(priBytes);
        KeyFactory priKeyFactory = KeyFactory.getInstance("RSA");
        PrivateKey priKey = priKeyFactory.generatePrivate(priKeySpec);
        return priKey;
    }


    /**
     * 签名方法
     *
     * @param appId       渠道内应用id
     * @param url         请求路径url
     * @param requestBody json参数请求体，无则传null
     * @param timeStamp   时间戳
     * @param privateKey  私钥
     * @return
     */
    public static String sign(String appId, String url, String requestBody, String timeStamp, String privateKey) throws Exception {
        //校验参数
        if (StringUtils.isBlank(appId)) {
            throw new CommonException(ErrorCodeEnum.ERROR_APP_ID_IS_NOT_EMPTY);
        }
        if (StringUtils.isBlank(url)) {
            throw new CommonException(ErrorCodeEnum.ERROR_URL_IS_NOT_EMPTY);
        }
        if (StringUtils.isBlank(timeStamp)) {
            throw new CommonException(ErrorCodeEnum.ERROR_TIME_STAMP_NOT_EMPTY);
        }
        if (StringUtils.isBlank(privateKey)) {
            throw new CommonException(ErrorCodeEnum.ERROR_PRIVATE_KEY_NOT_EMPTY);
        }

        TreeMap<String, String> resultMap = new TreeMap<String, String>();
        resultMap = urlSplit(url);
//        resultMap.put("appId",appId);
//        resultMap.put("timeStamp",timeStamp);

        //拼接请求body
        if (StringUtils.isNotBlank(requestBody)) {
            //将String转换成时间戳
            resultMap.put("bizContent", requestBody);
        }

        String dataStr = Common.forInSpiry(resultMap, appId, timeStamp);

        if (!StringUtils.isBlank(dataStr)) {
            PrivateKey priKey = getPrivateKeyByStr(privateKey);
            byte[] signInfo = sign(dataStr.getBytes(), priKey);
            dataStr = Base64.getEncoder().encodeToString(signInfo);
        }

        return dataStr;
    }

    /**
     * 测试
     *
     * @param args
     */
    public static void main(String[] args) throws Exception {

//        String[] keyPair = generateKeyPair();
//        System.out.println(keyPair[0]);
//        System.out.println(keyPair[1]);
        //TODO 1、参数加签示例
        String appId = "slfx";
//        String url = "http://localhost:7091/ums/v1/user/app/findUserAppList?projectId=103672";
        String url = "http://localhost:8083/client/shortUrl/getById?id=1";
        String requestBody = "";
        String publicKey ="MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDLat0TW2i7x8tLwa7ekd7VZPI9bbivr3JvJ5D+jroxYh8KYziQA8GlfeTZ9obdlIkbWpCHOE4nL8V1iBscNEgbXAJqgZ5d12RXPU67UOV1P1I4wLPm8HfEFIqogjihlQ4uRlbpS5e2XFWm8X5OXV3yp4OyiVOp1AhA2Vczm9VVbwIDAQAB";
        String privateKey = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAMtq3RNbaLvHy0vBrt6R3tVk8j1tuK+vcm8nkP6OujFiHwpjOJADwaV95Nn2ht2UiRtakIc4TicvxXWIGxw0SBtcAmqBnl3XZFc9TrtQ5XU/UjjAs+bwd8QUiqiCOKGVDi5GVulLl7ZcVabxfk5dXfKng7KJU6nUCEDZVzOb1VVvAgMBAAECgYAcXob+kB0HM1IS+qwctlNRh4aRMqAzQFDBV89M8TLeeETD+hqFW0DCh9BG5a7hcDm+6t6FPUEJDgyH27eMM25qwri9Uu9sXYsvx+wzAEgh4iULhQBNV2BxB/JvsDMSzdR9iuPrkwP1wXQsgDcT66c2gPKkjXFAtZWPmqoEdwHdIQJBAOrTd1MVbe6DXydRCGmBFAqtbHCsk64lE3IY5NySvjjOMI9j65CFzr9Leek/UtnhKUnPHOnzq10wf7R/SnczpdUCQQDdwmMAd7QOT59hDhYKmGpUJ9UbJzCJhv+4aw1ngNUbSheAAtTBeA8fSEzziAaHUicihNDGUAZW2WbWX5wIyBwzAkBV4UCighPSRvllx5zr+Oj7frFKA9N+vNB/ydrpUEGBROc9ia19DXKvQ0syopYKWO5gr/5DEXp6emI6ANObBKiBAkEAh8kcF9A0J0Mn4yTd6DT1fnw5Z/vyI3PPJ1wdhBAF7UTL6O1S2vgpwkziGYhj/v4VynQq5hNgHlPWPVOj1li5swJBAJw+YgDP+25zZG6HJUWx6fhuSO0HGcIMFymt5MS6tcD2Qv5s2HF0JLUp4vzDUtumU3v+d5xtI39HnB1n3jrEMg4=";
        String timeStamp = "1663118969272";
        String sign = sign(appId, url, requestBody, timeStamp, privateKey);
        //g3b3ngji0h0s2n8bfFjODPO0FiT+UtjXwLt8vNOAX0CSYSWkxyVGh1hGtKrH5gCjSNs7ArmI49Tx/6LP70gAzekUSRatHYsQQJUWy741Fk4XWpV7CrcNEehit9Q1Ii6CmbO+PYk2hFE+U4wUEx/A8joKYy4kA3+pI2nGtq2zv90=
        System.out.println(sign);

        //校验签名：获取签名的数据
        Map<String,String> resultMap=new HashMap<>();
        resultMap.put("id","1");
        String dataStr = Common.forInSpiry(resultMap,appId,timeStamp);
        byte[] decode = Base64.getDecoder().decode(sign);
        //校验签名
        boolean verify = RsaUtils.verify(dataStr.getBytes(), decode, getPublicKeyByStr(publicKey));
        System.out.println("校验签名值："+verify);

//        //TODO 2、密钥对生成示例
        String[] keyPair = genKeyPair();
        System.out.println("publicKey:"+keyPair[0]);
        System.out.println("privateKey:"+keyPair[1]);
    }

    /**
     * 截取url上的请求参数
     *
     * @param strURL
     * @return
     */
    private static String truncateUrlPage(String strURL) {
        String strAllParam = null;
        String[] arrSplit = null;
        //strURL=strURL.trim().toLowerCase();
        strURL = strURL.trim();
        arrSplit = strURL.split("[?]");
        if (strURL.length() > 1) {
            if (arrSplit.length > 1) {
                for (int i = 1; i < arrSplit.length; i++) {
                    strAllParam = arrSplit[i];
                }
            }
        }
        return strAllParam;
    }

    /**
     * 将url请求路径上的参数转成map型
     *
     * @param URL
     * @return
     */
    public static TreeMap<String, String> urlSplit(String URL) {
        TreeMap<String, String> mapRequest = new TreeMap<String, String>();
        String[] arrSplit = null;
        String strUrlParam = truncateUrlPage(URL);
        if (strUrlParam == null) {
            return mapRequest;
        }
        arrSplit = strUrlParam.split("[&]");
        for (String strSplit : arrSplit) {
            String[] arrSplitEqual = null;
            arrSplitEqual = strSplit.split("[=]");
            //解析出键值
            if (arrSplitEqual.length > 1) {
                //正确解析
                mapRequest.put(arrSplitEqual[0], arrSplitEqual[1]);
            } else {
                if (arrSplitEqual[0] != "") {
                    //只有参数没有值，不加入
                    mapRequest.put(arrSplitEqual[0], "");
                }
            }
        }
        return mapRequest;
    }

    /**
     * 随机生成密钥对
     *
     * @return 公钥，私钥
     * @throws NoSuchAlgorithmException
     */
    public static String[] genKeyPair() throws NoSuchAlgorithmException {
        // KeyPairGenerator类用于生成公钥和私钥对，基于RSA算法生成对象
        KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
        /**  初始化密钥对生成器，密钥大小为96-1024位  */
        keyPairGen.initialize(1024, new SecureRandom());
        /** 生成一个密钥对，保存在keyPair中  */
        KeyPair keyPair = keyPairGen.generateKeyPair();
        // 得到私钥
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        // 得到公钥
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        String publicKeyString = Base64.getEncoder().encodeToString(publicKey.getEncoded());
        /** 得到私钥字符串 */
        String privateKeyString = Base64.getEncoder().encodeToString((privateKey.getEncoded()));
        //keyMap.put(0,publicKeyString);  //0表示公钥
        //keyMap.put(1,privateKeyString);  //1表示私钥
        return new String[]{publicKeyString, privateKeyString};
    }
}
